package birdsong.comm;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.JOptionPane;

import birdsong.comm.ServerSideMessage.Type;

/**
 * Communication system for {@link birdsong.client.BirdsongClient Birdsong clients}. Birdsong clients must 
 * communicate with a Birdsong server exclusively through this class. 
 * 
 * @author While True
 * @version 1
 */

public class ClientCommunicator implements ClientComm {

	private BlockingQueue<ClientMessage> clientReceiverMessageList = new LinkedBlockingQueue<ClientMessage>();
	private BlockingQueue<ServerMessage> clientSenderMessageList = new LinkedBlockingQueue<ServerMessage>();

	private boolean isConnected = false;
	private Socket socket;
	private DealWithServer dealWithServer;
	private String clientNickname;
	private static final int SERVER_PORT = 8080;
	
	/**
	 * Establish a connection to a Birdsong server. This method is the first one called by 
	 * the client as no other operation (expect for {@link #isConnected()}) can be performed before
	 * a connection to the server has been established.
	 * 
	 * @param host                    Address of the server. For instance "localhost", "192.168.1.1", and so on.
	 * @param nickName                The nickname to use for the client.
	 * @throws UnknownHostException   Thrown if the host cannot be found.
	 * @throws IOException            Thrown if a connection cannot be established.
	 */
	@Override
	public void connect(String serverAddress, String nickName)	throws UnknownHostException, IOException {
	  if(!isConnected){
			clientNickname = nickName;
			InetAddress serverAdd = InetAddress.getByName(serverAddress);
			socket = new Socket(serverAdd, SERVER_PORT);
			isConnected = true;
			dealWithServer = new DealWithServer(socket, clientNickname, this);
			dealWithServer.start(); 
	  }
	  else 
		  JOptionPane.showMessageDialog(null, "CLIENTCOMMUNICATOR INFO: this client is already connected to the server.");
	} 

	/** 
	 * Check if currently connected to a host.
	 * 
	 * @return <b>true</b> if currently connected to a host, <b>false</b> otherwise.
	 */
	@Override
	public boolean isConnected() {
		return isConnected;
	}

	/**
	 * Disconnect from a Birdsong server. Any sockets are closed immediately. 
	 * Pending messages are not guaranteed to be delivered before disconnecting.
	 */	
	@Override
	public void disconnect() {
				
		ServerMessage clientDisconnectedMessage = new ServerMessage(Type.CLIENT_DISCONNECTED, null, clientNickname);
		getClientSenderMessageList().clear();
		getClientSenderMessageList().add(clientDisconnectedMessage);
		for (Thread t : dealWithServer.getActiveThreads()) {
			try { 
				t.join();
				isConnected = false;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
	}
	
	/**
	 * Get the next message received from the server. If no message 
	 * has been received, the method blocks until a message has 
	 * been received. If no message will ever be received (connection is down), null is returned.
	 * 
	 * @return The next message sent by the server to the client or null if no message will ever be received (disconnected from server).
	 */
	@Override
	public ClientSideMessage getNextMessage() {
		ClientMessage nextMessage = null;
		try { 
			nextMessage = getClientReceiverMessageList().take();
		} catch (InterruptedException e) {}
		return nextMessage;
	}

	/** 
	 * Check if a message from the server is pending. If {@link #hasNextMessage()} returns <b>true</b>, a call to {@link #getNextMessage()} 
	 * will return immediately with the oldest message in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call to {@link #getNextMessage()} 
	 * will block until a message has been received.
	 * 
	 * @return <b>true</b> if a message from the server is currently pending, otherwise <b>false</b>.
	 */
	@Override
	public boolean hasNextMessage() {
		return !getClientReceiverMessageList().isEmpty();
	}

	/**
	 * Send a Birdsong message from this client to the server which should then relay the message to all followers.
	 * 
	 * @param birdsongMessage The Birdsong message to be sent.
	 */
	@Override
	public void sendBirdsongMessage(String payload) {
		ServerMessage birdsongMessage = new ServerMessage(Type.BIRDSONG_MESSAGE, payload, clientNickname);
		getClientSenderMessageList().add(birdsongMessage);
	}

	/**
	 * Request to follow another client.
	 * 
	 * @param nickname the nickname of the client that should be followed.
	 */
	@Override
	public void follow(String nickname) {
		ServerMessage followMessage = new ServerMessage(Type.FOLLOW, nickname, clientNickname);
		getClientSenderMessageList().add(followMessage);
	}
	
	/**
	 * Request to unfollow a client.
	 * 
	 * @param nickname the nickname of the client currently followed that should be unfollowed.
	 */
	@Override
	public void unfollow(String nickname) {
		ServerMessage unfollowMessage = new ServerMessage(Type.UNFOLLOW,nickname, clientNickname);
		getClientSenderMessageList().add(unfollowMessage);

	}

	/** Getter that returns the list which contains all the messages of the client received from the Birdsong server.
	 * 
	 * @return Returns the list with the messages received from the server.
	 */
	public BlockingQueue<ClientMessage> getClientReceiverMessageList() {
		return clientReceiverMessageList;
	}

	/** Getter that returns the list which contains all the messages of the client sent to the Birdsong server.
	 * 
	 * @return Returns the list with the messages sent to the Birdsong server.
	 */
	public BlockingQueue<ServerMessage> getClientSenderMessageList() {
		return clientSenderMessageList;
	}
}